
/**
 * AppleIIGo
 * Disk II Controller class
 * (C) 2007-2008 by Marc S. Ressl (mressl@umich.edu)
 * Released under the GPL
 * Inspired by Jim Sather's "Understanding the Apple II"
 */

#include <string.h>
#include "DiskIIController.h"
#include "DiskPort.h"
#include "SmartPort.h"

#include "Apple525DiskDrive.h"
#include "Apple35DiskDrive.h"
#include "SmartPortDiskDrive.h"
#include "UniDisk35DiskDrive.h"

DiskIIController::DiskIIController(Motherboard * motherboard,
								   char * slotDeviceName) :
SlotDevice(motherboard, slotDeviceName) {
	isDiskIIController = true;
	
	diskDrives = new List();
}

DiskIIController::~DiskIIController() {
	for (diskDrives->setStart(); diskDrives->isNext(); diskDrives->next())
		delete (DiskDrive *) diskDrives->get();
	delete diskDrives;
}

void DiskIIController::onResetAsserted(bool isRestart) {
	motorOffFrameIndex = 0;
	
	phaseRegister = 0;
	isPhaseRegisterUpdated = false;
	isDriveEnabled = false;
	selectedPort->setDriveEnabled(false);
	isDrive2Value = false;
	sequencerRegister = 0;
	sequencerState = SEQUENCERSTATE_OTHER;
	shiftRegister = 0;
	
	isLatchMode = false;
	isAsynchronousMode = false;
	isDriveTimerEnabled = true;
	bitCellShift = 3;
	isDrive35Enabled = false;
	isWriteDataReady = true;
	isWriteUnderrun = false;
	readDataRegister = 0;
	writeDataRegister = 0;
	writeDataBitNum = 0;
	
	updateSelectedPort();
}

void DiskIIController::onFrameEnd() {
	if (!motorOffFrameIndex)
		return;
	
	motorOffFrameIndex--;
	if (!motorOffFrameIndex) {
		isDriveEnabled = false;
		selectedPort->setDriveEnabled(false);
	}
}

void DiskIIController::writeIo(UINT16 address, UINT8 value) {
	// to-do: remove debugging
//	printf("DISK W %02x:%02x\n", address & 0xff, value);
	updateState();
	
	switch (address & 0xf) {
		case 0x0: case 0x1: case 0x2: case 0x3:
		case 0x4: case 0x5: case 0x6: case 0x7:
			setPhase((address >> 1) & 0x3, address & 0x1);
			break;
		case 0x8: case 0x9:
			setDriveEnable(address & 0x1);
			break;
		case 0xa: case 0xb:
			setDrive2(address & 0x1);
			break;
		case 0xc: case 0xd:
			setSequencer(SEQUENCERREGISTER_LOAD, address & 0x1);
			break;
		case 0xe: case 0xf:
			setSequencer(SEQUENCERREGISTER_WRITE, address & 0x1);
			break;
	}
	
	if (address & 0x1)
		setRegister(value);
}

UINT8 DiskIIController::readIo(UINT16 address) {
	// to-do: remove debugging
	updateState();
	
	switch (address & 0xf) {
		case 0x0: case 0x1: case 0x2: case 0x3:
		case 0x4: case 0x5: case 0x6: case 0x7:
			setPhase((address >> 1) & 0x3, address & 0x1);
			break;
		case 0x8: case 0x9:
			setDriveEnable(address & 0x1);
			break;
		case 0xa: case 0xb:
			setDrive2(address & 0x1);
			break;
		case 0xc: case 0xd:
			setSequencer(SEQUENCERREGISTER_LOAD, address & 0x1);
			break;
		case 0xe: case 0xf:
			setSequencer(SEQUENCERREGISTER_WRITE, address & 0x1);
			break;
	}
	
	UINT8 value;
	if (address & 0x1)
		value = timingGenerator->readFloatingBus();
	else
		value = setRegister(0x00);
//	printf("DISK R %02x:%02x\n", address & 0xff, value);
	return value;
}

void DiskIIController::connectDrives(char * driveList[]) {
	for (UINT32 index = 0; index < DISKPORT_END; index++)
		diskPort[index] = 0;
	
	for (; *driveList; driveList++)
		connectDrive(*driveList);
	
	class DiskPort * emptyDiskPort = new DiskPort();
	diskDrives->insert(emptyDiskPort);
	for (UINT32 index = 0; index < DISKPORT_END; index++)
		if (!diskPort[index])
			diskPort[index] = emptyDiskPort;
	
	selectedPort = diskPort[0];
}

void DiskIIController::connectDrive(char * driveType) {
	DiskDrive * drive;
	
	if (!strcmp(driveType, "Apple525DiskDrive"))
		drive = new Apple525DiskDrive();
	else if (!strcmp(driveType, "Apple35DiskDrive"))
		drive = new Apple35DiskDrive();
	else if (!strcmp(driveType, "UniDisk35DiskDrive"))
		drive = new UniDisk35DiskDrive();
	else if (!strcmp(driveType, "SmartPortDiskDrive"))
		drive = new SmartPortDiskDrive();
	else
		return;
	
	if (drive->getPort() == DISKDRIVEPORT_DISKPORT) {
		UINT32 diskPortIndex = ((drive->getType() == (DISKDRIVETYPE_3_5)) * 2);
		if (diskPort[diskPortIndex]) {
			diskPortIndex++;
			if (diskPort[diskPortIndex]) {
				delete drive;
				return;
			}
		}
		diskPort[diskPortIndex] = (FloppyDiskDrive *) drive;
	} else {
		if (!diskPort[DISKPORT_SMARTPORT])
			diskDrives->insert(diskPort[DISKPORT_SMARTPORT] = new SmartPort());
		
		SmartPort * smartPort = (SmartPort *) diskPort[DISKPORT_SMARTPORT];
		smartPort->connectDrive((SmartPortUnit *) drive);
	}
	
	diskDrives->insert(drive);
	motherboard->registerDiskDrive(drive);
}

void DiskIIController::updateSelectedPort() {
	bool isSmartPortEnabled = false;
	if (!isDiskIIController && !isDrive35Enabled) {
		if (phaseRegister == (PHASE0 | PHASE2))
			diskPort[DISKPORT_SMARTPORT]->reset();
		
		isSmartPortEnabled = (getBit(phaseRegister, PHASE1) &&
							  getBit(phaseRegister, PHASE3));
	}
	
	DiskPort * port;
	if (isDrive35Enabled)
		port = diskPort[DISKPORT_DRIVE3_5_2 - isDrive2Value];
	else if (isSmartPortEnabled)
		port = diskPort[DISKPORT_SMARTPORT];
	else
		port = diskPort[DISKPORT_DRIVE5_25_1 + isDrive2Value];
	
	if ((port != selectedPort) && isDriveEnabled) {
		selectedPort->setDriveEnabled(false);
		selectedPort = port;
		selectedPort->setDriveEnabled(true);
	} else
		selectedPort = port;
	
	selectedPort->setPhaseRegister(phaseRegister);
}

void DiskIIController::setPhase(UINT32 index, bool value) {
	printf("PHASE %01x:%01x\n", index, value);
	setBit(phaseRegister, 1 << index, value);
	
	updateSelectedPort();
	
	lastPhaseQ3ClockIndex = timingGenerator->getQ3ClockIndex();
	isPhaseRegisterUpdated = true;
}

void DiskIIController::setDriveEnable(bool value) {
	printf("DRIVE ENABLED: %01x\n", value);
	if (value) {
		motorOffFrameIndex = 0;
		lastSequencerQ3ClockIndex = timingGenerator->getQ3ClockIndex();
		
		lastPhaseQ3ClockIndex = timingGenerator->getQ3ClockIndex();
		isPhaseRegisterUpdated = true;
		
		isDriveEnabled = true;
		selectedPort->setDriveEnabled(true);
	} else {
		if (isDriveTimerEnabled)
			motorOffFrameIndex = (UINT32) (timingGenerator->getFrameRate() +
										   0.5);
		else {
			isDriveEnabled = false;
			selectedPort->setDriveEnabled(false);
		}
	}
}

void DiskIIController::setDrive2(bool value) {
	printf("DRIVE SELECT: %01x\n", value);
	isDrive2Value = value;
	
	updateSelectedPort();
}

void DiskIIController::setSequencer(UINT32 index, bool value) {
	setBit(sequencerRegister, index, value);
}

void DiskIIController::updateState() {
	if (!isDriveEnabled)
		return;
	
	UINT64 q3ClockIndex = timingGenerator->getQ3ClockIndex();
	
	if (isPhaseRegisterUpdated &&
		((q3ClockIndex - lastPhaseQ3ClockIndex) >= 1000)) {
		selectedPort->onPhysicalPhaseUpdate();
		isPhaseRegisterUpdated = false;
	}
	
	UINT32 bitNum = ((q3ClockIndex - (lastSequencerQ3ClockIndex & (~0x7))) >>
					 bitCellShift);
	
	switch (sequencerRegister) {
		case (SEQUENCERREGISTER_READ | SEQUENCERREGISTER_SHIFT):
			selectedPort->updateReceiveBitNum(&bitNum);
			
			while (bitNum--) {
				bool bit = selectedPort->receiveBit();
				
				switch (sequencerState) {
					case SEQUENCERSTATE_QAWAIT:
						if (bit)
							sequencerState--;
						break;
					case SEQUENCERSTATE_BIT6:
						sequencerState--;
						shiftRegister = 0x01;
					default:
						shiftRegister <<= 1;
						shiftRegister |= bit;
						if (shiftRegister & 0x80) {
							sequencerState = SEQUENCERSTATE_QAWAIT;
							if (isLatchMode)
								readDataRegister = shiftRegister;
						}
						break;
				}
			}
			break;
		case (SEQUENCERREGISTER_READ | SEQUENCERREGISTER_LOAD): {
			selectedPort->discardBits(bitNum);
			
			if (!isDiskIIController)
				break;
			
			SINT32 q3ClockNum = (q3ClockIndex - lastSequencerQ3ClockIndex - 1);
			if (q3ClockNum > 8)
				q3ClockNum = 8;
			for (SINT32 index = 0; index < q3ClockNum; index++) {
				shiftRegister >>= 1;
				shiftRegister |= (selectedPort->senseInput() << 7);
			}
			break;
		}
		case (SEQUENCERREGISTER_WRITE | SEQUENCERREGISTER_SHIFT):
		case (SEQUENCERREGISTER_WRITE | SEQUENCERREGISTER_LOAD):
			selectedPort->updateSendBitNum(&bitNum);
			
			if (isWriteUnderrun) {
				selectedPort->discardBits(bitNum);
				break;
			}
			
			while (bitNum--) {
				if (isAsynchronousMode) {
					if (!writeDataBitNum) {
						if (!isWriteDataReady) {
							isWriteDataReady = true;
							shiftRegister = writeDataRegister;
							writeDataBitNum = 8 - 1;
						} else {
							isWriteUnderrun = true;
							selectedPort->discardBits(bitNum);
							break;
						}
					} else
						writeDataBitNum--;
				}
				
				bool bit = shiftRegister & 0x80;
				shiftRegister <<= 1;
				
				selectedPort->sendBit(bit);
			}
			break;
	}
	
	lastSequencerQ3ClockIndex = q3ClockIndex;
}

UINT8 DiskIIController::setRegister(UINT8 value) {
	if (!isDriveEnabled)
		return 0xff;
	
	switch (sequencerRegister) {
		case (SEQUENCERREGISTER_READ | SEQUENCERREGISTER_LOAD):
			shiftRegister >>= 1;
			shiftRegister |= (selectedPort->senseInput() << 7);
			break;
		case (SEQUENCERREGISTER_WRITE | SEQUENCERREGISTER_LOAD):
			shiftRegister = value;
			break;
	}
	
	return shiftRegister;
}
